/****************************************************************************
 * arch/arm/src/str71x/str71x_head.S
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.  The
 * ASF licenses this file to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the
 * License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations
 * under the License.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <nuttx/config.h>		/* NuttX configuration settings */

#include "arm.h"			/* ARM-specific settings */
#include "chip.h"			/* Chip-specific settings */

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

/* This file holds the NuttX start logic that runs when the STR711
 * is reset.  This logic must be located at address 0x4000:0000 in
 * flash.  It will also be mapped to address zero when the STR711 is
 * reset.
 */

/****************************************************************************
 * External references
 ****************************************************************************/

	.globl	str71x_prccuinit			/* Clock initialization */
	.globl	up_lowsetup				/* Early initialization of UART */
#ifdef USE_EARLYSERIALINIT
	.globl	arm_earlyserialinit			/* Early initialization of serial driver */
#endif
#ifdef CONFIG_ARCH_LEDS
	.globl	board_autoled_initialize		/* Boot LED setup */
#endif
#ifdef CONFIG_DEBUG_FEATURES
	.globl	arm_lowputc				/* Low-level debug output */
#endif
	.globl	nx_start				/* NuttX entry point */

/****************************************************************************
 * Macros
 ****************************************************************************/

/****************************************************************************
 * Name: showprogress
 *
 * Description:
 *   Print a character on the UART to show boot status. This macro will
 *   modify r0, r1, r2 and r14
 *
 ****************************************************************************/

	.macro	showprogress, code
#ifdef CONFIG_DEBUG_FEATURES
	mov		r0, #\code
	bl		arm_lowputc
#endif
	.endm

/****************************************************************************
 * Name: emiinit
 *
 * Description:
 *   Initialize external memory banks 0-3 as configured
 *
 ****************************************************************************/

	.macro	emiinit, base, value
#if defined(CONFIG_STR71X_BANK0) || defined(CONFIG_STR71X_BANK1) || \
    defined(CONFIG_STR71X_BANK2) || defined(CONFIG_STR71X_BANK3)

	/* In order to use the external memory, certain GPIO pins must be
	 * configured in the alternate function:
	 *
	 * GPIO    ALT     Description
	 * P2.0-3  CS.0-3  External memory chip select for banks 0,1,3,4
	 * P2.4-7  A.20-23 External memory extended address bus (needed for
	 *                 address space > 1Mb)
	 */

#ifdef CONFIG_STR71X_BIGEXTMEM
#  define EXTMEM_GPIO_BITSET 0x000000ff		/* P2.0-7 */
#else
#  define EXTMEM_GPIO_BITSET 0x0000000f		/* P2.0-3 */
#endif

	ldr		\base, =STR71X_GPIO_BASE		/* Configure P2.0 to P2.3/7 in AF_PP mode */
	ldr		\value, [\base, #STR71X_GPIO_PC0_OFFSET]
	orr		\value, \value, #EXTMEM_GPIO_BITSET
	str		\value, [\base, #STR71X_GPIO_PC0_OFFSET]
	ldr		\value, [\base, #STR71X_GPIO_PC1_OFFSET]
	orr		\value, \value, #EXTMEM_GPIO_BITSET
	str		\value, [\base, #STR71X_GPIO_PC1_OFFSET]
	ldr		\value, [\base, #STR71X_GPIO_PC2_OFFSET]
	orr		\value, \value, #EXTMEM_GPIO_BITSET
	str		\value, [\base, #STR71X_GPIO_PC2_OFFSET]

	/* Enable bank 0 */

	ldr		\base, =STR71X_EMI_BASE

#ifdef CONFIG_STR71X_BANK0

	/* Get the bank 0 size */

#  if CONFIG_STR71X_BANK0_SIZE == 8
#    define EXTMEM_BANK0_SIZE STR71X_EMIBCON_BSIZE8
#  elif CONFIG_STR71X_BANK0_SIZE == 16
#    define EXTMEM_BANK0_SIZE STR71X_EMIBCON_BSIZE16
#  else
#    error "CONFIG_STR71X_BANK0_SIZE invalid"
#  endif

	/* Get the bank 0 waitstates */

#  if !defined(CONFIG_STR71X_BANK0_WAITSTATES) || \
      CONFIG_STR71X_BANK0_WAITSTATES < 0 || CONFIG_STR71X_BANK0_WAITSTATES > 15
#    error "CONFIG_STR71X_BANK0_WAITSTATES invalid"
#  else
     /* Bits 2-5: wait states */
#    define EXTMEM_BANK0_WAITSTATES (CONFIG_STR71X_BANK0_WAITSTATES << 2)
#  endif

	ldr		\value, =(STR71X_EMIBCON_ENABLE|EXTMEM_BANK0_WAITSTATES|EXTMEM_BANK0_SIZE)
#else
	mov		\value, #0
#endif
	str		\value, [\base, #STR71X_EMI_BCON0_OFFSET]

	/* Enable bank 1 */

#ifdef CONFIG_STR71X_BANK1

	/* Get the bank 1 size */

#  if CONFIG_STR71X_BANK1_SIZE == 8
#    define EXTMEM_BANK1_SIZE STR71X_EMIBCON_BSIZE8
#  elif CONFIG_STR71X_BANK1_SIZE == 16
#    define EXTMEM_BANK1_SIZE STR71X_EMIBCON_BSIZE16
#  else
#    error "CONFIG_STR71X_BANK1_SIZE invalid"
#  endif

	/* Get the bank 1 waitstates */

#  if !defined(CONFIG_STR71X_BANK1_WAITSTATES) || \
      CONFIG_STR71X_BANK1_WAITSTATES < 0 || CONFIG_STR71X_BANK1_WAITSTATES > 15
#    error "CONFIG_STR71X_BANK1_WAITSTATES invalid"
#  else
     /* Bits 2-5: wait states */
#    define EXTMEM_BANK1_WAITSTATES (CONFIG_STR71X_BANK1_WAITSTATES << 2)
#  endif

	ldr		\value, =(STR71X_EMIBCON_ENABLE|EXTMEM_BANK1_WAITSTATES|EXTMEM_BANK1_SIZE)
#else
	mov		\value, #0
#endif
	str		\value, [\base, #STR71X_EMI_BCON1_OFFSET]

	/* Enable bank 2 */

#ifdef CONFIG_STR71X_BANK2

	/* Get the bank 2 size */

#  if CONFIG_STR71X_BANK2_SIZE == 8
#    define EXTMEM_BANK2_SIZE STR71X_EMIBCON_BSIZE8
#  elif CONFIG_STR71X_BANK2_SIZE == 16
#    define EXTMEM_BANK2_SIZE STR71X_EMIBCON_BSIZE16
#  else
#    error "CONFIG_STR71X_BANK2_SIZE invalid"
#  endif

	/* Get the bank 2 waitstates */

#  if !defined(CONFIG_STR71X_BANK2_WAITSTATES) || \
      CONFIG_STR71X_BANK2_WAITSTATES < 2 || CONFIG_STR71X_BANK2_WAITSTATES > 15
#    error "CONFIG_STR71X_BANK2_WAITSTATES invalid"
#  else
     /* Bits 2-5: wait states */
#    define EXTMEM_BANK2_WAITSTATES (CONFIG_STR71X_BANK2_WAITSTATES << 2)
#  endif

	ldr		\value, =(STR71X_EMIBCON_ENABLE|EXTMEM_BANK2_WAITSTATES|EXTMEM_BANK2_SIZE)
#else
	mov		\value, #0
#endif
	str		\value, [\base, #STR71X_EMI_BCON2_OFFSET]

	/* Enable bank 3 */

#ifdef CONFIG_STR71X_BANK3

	/* Get the bank 3 size */

#  if CONFIG_STR71X_BANK3_SIZE == 8
#    define EXTMEM_BANK3_SIZE STR71X_EMIBCON_BSIZE8
#  elif CONFIG_STR71X_BANK3_SIZE == 16
#    define EXTMEM_BANK3_SIZE STR71X_EMIBCON_BSIZE16
#  else
#    error "CONFIG_STR71X_BANK3_SIZE invalid"
#  endif

	/* Get the bank 3 waitstates */

#  if !defined(CONFIG_STR71X_BANK3_WAITSTATES) || \
      CONFIG_STR71X_BANK3_WAITSTATES < 3 || CONFIG_STR71X_BANK3_WAITSTATES > 15
#    error "CONFIG_STR71X_BANK3_WAITSTATES invalid"
#  else
     /* Bits 2-5: wait states */
#    define EXTMEM_BANK3_WAITSTATES (CONFIG_STR71X_BANK3_WAITSTATES << 2)
#  endif

	ldr		\value, =(STR71X_EMIBCON_ENABLE|EXTMEM_BANK3_WAITSTATES|EXTMEM_BANK3_SIZE)
#else
	mov		\value, #0
#endif
	str		\value, [\base, #STR71X_EMI_BCON3_OFFSET]
#endif
	.endm

/****************************************************************************
 * Name: eicinit
 *
 * Description:
 *   The EIC is initialized for use with NuttX.  This initialization does not
 *   take advantage of the high performance capabilities of the EIC.  Instead,
 *   The EIC is only used to provide NuttX IRQ numbers.  Here is what is
 *   done:
 *
 *     IRQs and FIQs are disabled
 *     IVR set to zero
 *     All channels are disabled
 *     Channels set to priority 0
 *     All SIR[n] registers contain the NuttX IRQ number in the MS 16-bits
 *
 *   At the time of IRQ processing, the IVR will contain the decoded IRQ
 *   number needed by NuttX.
 *
 ****************************************************************************/

	.macro	eicinit, eicbase, value, irqno, offset
	/* Disable and clear all interrupts */

	ldr		\eicbase, =STR71X_EIC_BASE

	/* Disable FIQ and IRQ */

	mov		\value, #0
	str		\value, [\eicbase, #STR71X_EIC_ICR_OFFSET]

	/* Disable all channel interrupts */

	str		\value, [\eicbase, #STR71X_EIC_IER_OFFSET]

	/* Clear all pending IRQs */

	ldr		\value, =0xffffffff
	str		\value, [\eicbase, #STR71X_EIC_IPR_OFFSET]

	/* Disable FIQ channels/clear pending FIQs */

	mov		\value, #0x0c
	str		\value, [\eicbase, #STR71X_EIC_FIR_OFFSET]

	/* Reset the current priority register */

	mov		\value, #0
	str		\value, [\eicbase, #STR71X_EIC_CIPR_OFFSET]

	/* Zero IVR 31:16 */

	str		\value, [\eicbase, #STR71X_EIC_IVR_OFFSET]

	/* Set up the loop to initialize each SIR register.  Start
	 * with IRQ number 0 and SIR0
	 */

	mov		\irqno, #0
	ldr		\offset, =STR71X_EIC_SIR_OFFSET

	/* Then loop for each EIC channel */
eicloop:
	/* Shift the IRQ number to bits 16-31 and save the shifted IRQ
	 * number as SIR[irqno].  This will appear as bits 0:15 in the
	 * IVR during IRQ processing.
	 *
	 * NOTE that the initial priority is set to zero -- the current
	 * interrupt priority (CIP) is always zero, so these interrupts
	 * are all disabled.
	 */

	mov		\value, \irqno, lsl #16
	str		\value, [\eicbase, \offset]

	/* Increment the offset to the next SIR register and inrement
	 * the IRQ number.
	 */

	add		\offset, \offset, #4
	add		\irqno, \irqno, #1

	/* Continue to loop until all of the SIR registers have been
	 * initializeed.
	 */

	cmp		\irqno, #STR71X_EIC_NCHANNELS
	blt		eicloop
	.endm

/****************************************************************************
 * Name: periphinit
 *
 * Description:
 *   Disable all perfipherals (except EIC)
 *
 ****************************************************************************/

	.macro	periphinit, value, base1, base2
#ifndef CONFIG_STR71X_DISABLE_PERIPHINIT
	/* Set up APB1 and APB2 addresses */

	ldr		\base1, =STR71X_APB1_BASE
	ldr		\base2, =STR71X_APB2_BASE

	/* Disable all APB1 peripherals */

	ldr		\value, =STR71X_APB1_APB1ALL
	strh		\value, [\base1, #STR71X_APB_CKDIS_OFFSET]

	/* Disable all(or most) APB2 peripherals */

	ldr		\value, =(STR71X_APB2_APB2ALL & ~STR71X_APB2_EIC)
	strh		\value, [\base2, #STR71X_APB_CKDIS_OFFSET]

	/* Allow EMI and USB */

	ldr		\base1, =STR71X_RCCU_BASE
#ifdef CONFIG_STR71X_USB
	ldr		\value, =(STR71X_RCCUPER_EMI|STR71X_RCCUPER_USBKERNEL)
#else
	ldr		\value, =STR71X_RCCUPER_EMI
#endif
	strh		\value, [\base1, #STR71X_RCCU_PER_OFFSET]
#endif
	.endm

/****************************************************************************
 * Name: remap
 *
 * Description:
 *   Remap memory at address 0x0000000 to either FLASH.  The system always
 *   boots at Bank0, sector 0 of FLASH.  Part of the initial setup will be to
 *   map the memory appropriately for the execution configuration.  Various
 *   options are possible, but only boot from FLASH is currently supported.
 *
 ****************************************************************************/

	.macro	remap, base, value

	/* Read the PCU BOOTCR register */

	ldr		\base, =STR71X_PCU_BASE
	ldrh		\value, [\base, #STR71X_PCU_BOOTCR_OFFSET]

	/* Mask out the old boot mode bits and set the boot mode to FLASH */

	bic		\value, \value, #STR71X_PCUBOOTCR_BOOTMASK
	orr		\value, \value, #STR71X_PCUBOOTCR_BMFLASH

	/* Save the modified BOOTCR register */

	strh		\value, [\base, #STR71X_PCU_BOOTCR_OFFSET]
	.endm

/****************************************************************************
 * Text
 ****************************************************************************/

	.text

/****************************************************************************
 * Name: _vector_table
 *
 * Description:
 *   Interrupt vector table.  This must be located at the beginning
 *   of the memory space (at the beginning FLASH which will be mapped to
 *   address 0x00000000).  The first entry in the vector table is the reset
 *   vector and this is the code that will execute when the processor is reset.
 *
 ****************************************************************************/

	.globl	_vector_table
	.type	_vector_table, %function
_vector_table:
	ldr		pc, .Lresethandler		/* 0x00: Reset */
	ldr		pc, .Lundefinedhandler		/* 0x04: Undefined instruction */
	ldr		pc, .Lswihandler		/* 0x08: Software interrupt */
	ldr		pc, .Lprefetchaborthandler	/* 0x0c: Prefetch abort */
	ldr		pc, .Ldataaborthandler		/* 0x10: Data abort */
	.long	0					/* 0x14: Reserved vector */
	ldr		pc, .Lirqhandler		/* 0x18: IRQ */
	ldr		pc, .Lfiqhandler		/* 0x1c: FIQ */

	.globl	__start
	.globl	arm_vectorundefinsn
	.globl	arm_vectorsvc
	.globl	arm_vectorprefetch
	.globl	arm_vectordata
	.globl	arm_vectorirq
	.globl	arm_vectorfiq

.Lresethandler:
	.long	__start
.Lundefinedhandler:
	.long	arm_vectorundefinsn
.Lswihandler:
	.long	arm_vectorsvc
.Lprefetchaborthandler:
	.long	arm_vectorprefetch
.Ldataaborthandler:
	.long	arm_vectordata
.Lirqhandler:
	.long	arm_vectorirq
.Lfiqhandler:
	.long	arm_vectorfiq
	.size	_vector_table, . - _vector_table

/****************************************************************************
 * Name: __start
 *
 * Description:
 *   Reset entry point.  This is the first function to execute when
 *   the processor is reset.  It initializes hardware and then gives
 *   control to NuttX.
 *
 ****************************************************************************/

	.global __start
	.type	__start, #function

__start:
	/* On reset, an aliased copy of FLASH is mapped to address 0x00000000.
	 *  Continue execution in the 'real' FLASH address space rather than
	 *  the aliased copy
	 */

	ldr		pc, =__flashstart
__flashstart:
	.rept 9
	nop				/* Wait for OSC stabilization */
	.endr

	/* Setup the initial processor mode */

	mov		r0, #(PSR_MODE_SVC | PSR_I_BIT | PSR_F_BIT )
	msr		cpsr, r0

	/* Initialize the external memory interface (EMI) */

	emiinit		r0, r1

	/* Initialize the enhanced interrupt controller (EIC) */

	eicinit		r0, r1, r2, r3

	/* Disable all peripherals except EIC */

	periphinit	r0, r1, r2

	/* Map memory appropriately for configuration */

	remap		r0, r1

	/* Setup system stack (and get the BSS range) */

	adr		r0, LC0
	ldmia		r0, {r4, r5, sp}

	/* Clear system BSS section */

	mov		r0, #0
1:	cmp		r4, r5
	strcc		r0, [r4], #4
	bcc		1b

	/* Copy system .data sections from FLASH to new home in RAM. */

	adr		r3, LC2
	ldmia		r3, {r0, r1, r2}

2:	ldmia		r0!, {r3 - r10}
	stmia		r1!, {r3 - r10}
	cmp		r1, r2
	blt		2b

	/* Initialize clocking */

	bl		str71x_prccuinit

	/* Configure the uart so that we can get debug output as soon
	 * as possible.
	 */

	bl		up_lowsetup
	showprogress	'A'

	/* Perform early serial initialization */

	mov		fp, #0
#ifdef USE_EARLYSERIALINIT
	bl		arm_earlyserialinit
#endif

	showprogress	'B'

	/* Call C++ constructors */

#ifdef CONFIG_CPLUSPLUS
	ldr		r0, =__ctors_start__
	ldr		r1, =__ctors_end__
ctor_loop:
	cmp		r0, r1
	beq		ctor_end
	ldr		r2, [r0], #4
	stmfd		sp!, {r0-r1}
	mov		lr, pc
	mov		pc, r2
	ldmfd		sp!, {r0-r1}
	b		ctor_loop
ctor_end:

	showprogress	'C'
#endif
	showprogress	'\n'

	/* Initialize onboard LEDs */

#ifdef CONFIG_ARCH_LEDS
	bl		board_autoled_initialize
#endif

	/* Then jump to OS entry */

	b		nx_start

	/* Call destructors  -- never get here */

#if 0 /* CONFIG_CPLUSPLUS */
	ldr		r0, =__dtors_start__
	ldr		r1, =__dtors_end__
dtor_loop:
	cmp		r0, r1
	beq		dtor_end
	ldr		r2, [r0], #4
	stmfd		sp!, {r0-r1}
	mov		lr, pc
	mov		pc, r2
	ldmfd		sp!, {r0-r1}
	b		dtor_loop
dtor_end:
#endif

	/* Variables:
	 * _sbss is the start of the BSS region (see ld.script)
	 * _ebss is the end of the BSS region (see ld.script)
	 * The idle task stack starts at the end of BSS and is
	 * of size CONFIG_IDLETHREAD_STACKSIZE.  The heap continues
	 * from there until the end of memory.  See g_idle_topstack
	 * below.
	 */

LC0:	.long	_sbss
	.long	_ebss
	.long	_ebss+CONFIG_IDLETHREAD_STACKSIZE

LC2:	.long	_eronly	/* Where .data defaults are stored in FLASH */
	.long	_sdata	/* Where .data needs to reside in SDRAM */
	.long	_edata
	.size	__start, .-__start

	/* This global variable is unsigned long g_idle_topstack and is
	 * exported from here only because of its coupling to LCO
	 * above.
	 */

	.data
	.align	4
	.globl	g_idle_topstack
	.type	g_idle_topstack, object
g_idle_topstack:
	.long	_ebss+CONFIG_IDLETHREAD_STACKSIZE
	.size	g_idle_topstack, .-g_idle_topstack

	.end
